/*
Copyright (C) 2011 Salil Bhagurkar

This file is part of illusion

illusion is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

illusion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * Kernel physical page allocator
 */

#include <klib/lib.h>
#include <klib/bitmap.h>
#include <klib/memory.h>
#include <klib/format.h>
#include <kernel/errors.h>
#include <kernel/page.h>
#include <kernel/console.h>
#include <kernel/init.h>
#include <kernel/list.h>
#include <kernel/kmalloc.h>
#include <kernel/sys.h>

/*
 * Page bitmap that keeps a track of 4096 pages : 16 MiB
 */

static struct pagemap base_pagemap = {
		.base = 0x0,
		.bitmap = {0},
		.next = null,
		.prev = null,
		.free_pages = MAP_NR_PAGES,
};

static struct pagemap *pagemaps = &base_pagemap, *pagemaps_tail = &base_pagemap;
static uint_t nr_maps = 1;

#define bit_no(address) (((ptr_t)address % PAGEMAP_RANGE) / PAGE_SIZE)
#define address_from_bit(bit_no) (void *)(bit_no * PAGE_SIZE)
#define address_wr_from_bit(map_base, bit_no) (void *)(map_base + bit_no * PAGE_SIZE)
#define address_range_base(address) ((ptr_t)address & PAGEMAP_ADDRESS_MASK)

void debug_phys_pagemaps()
{
	struct pagemap *p;
	list_for(pagemaps, p) {
		debug("Pagemap:\n");
		debug("base = 0x%x\n", p->base);
		debug("free_pages = %u\n", p->free_pages);
	}
}

/*
 * Function to create a new map when required
 * If the map is provided as null, a new map is allocated.
 * For allocation to succeed, this function is always called when there is
 * some space left for the new pagemap.
 */
void phys_create_range(struct pagemap *map, ptr_t address)
{
	if(address % PAGEMAP_SZ != 0)
		assertv(false, "Range should be %u aligned\n", PAGEMAP_SZ);
	if(map == null) {
		//Check the init_state to confirm we can use the allocation system
		assertv(init_state >= INIT_MAP,
				"INIT_MAP not reached for phys range\n");
		map = (struct pagemap *)kmalloc(sizeof(struct pagemap), "phmap");
	}
	map->base = (void *)address;
	memset(map->bitmap, 0, PAGEMAP_SZ);
	map->free_pages = MAP_NR_PAGES;
	map->next = map->prev = null;
	list_attach(pagemaps, pagemaps_tail, map);
	nr_maps++;
}

static struct pagemap *get_map(void *address)
{
	struct pagemap *p;
	list_for(pagemaps, p) {
		if(address >= p->base && address < (p->base + PAGEMAP_RANGE))
			return p;
	}
	return null;
}


/*
 * Function to check if a page is reserved
 */
int phys_is_page(void *address)
{
	uint_t bit = bit_no(address);
	struct pagemap *map = get_map(address);
	if(!map)
		return 0;
	if(get_bit(map->bitmap, bit))
		return 1;
	return 0;
}

int phys_reserve_page(void *address)
{
	uint_t bit = bit_no(address);
	struct pagemap *map = get_map(address);
	if(!map) {
		ptr_t address_l = (ptr_t)address;
		//Create the range
		debug("Creating phys range for 0x%x\n", address_l);
		phys_create_range(null, address_l & PAGEMAP_ADDRESS_MASK);
		map = get_map(address);
	} else {
		//Sanity check
		assertv(!get_bit(map->bitmap, bit), "Page already reserved: 0x%x\n", address);
	}
	set_bit(map->bitmap, bit);
	map->free_pages--;
	return 0;
}

int phys_free_page(void *address)
{
	uint_t bit = bit_no(address);
	struct pagemap *map = get_map(address);
	if(!map)
		assertv(false,
				"No map exists for freeing page 0x%x\n", address);
	assertv(get_bit(map->bitmap, bit), "Page already free: 0x%x\n", address);
	clear_bit(map->bitmap, bit);
	map->free_pages++;
	return 0;
}

uint_t phys_count_free()
{
	struct pagemap *p;
	uint_t count = 0;
	list_for(pagemaps, p) {
		count += count_clear_bit(p->bitmap, MAP_NR_PAGES);
	}
	return count;
}

uint_t phys_total_count()
{
	return nr_maps * MAP_NR_PAGES;
}

//TODO Optimize this
int phys_reserve_pages(void *address, uint_t count)
{
	void *address_i = address;
	while(count--) {
		phys_reserve_page(address_i);
		address_i += PAGE_SIZE;
	}
	return 0;
}

int phys_free_pages(void *address, uint_t count)
{
	void *address_i = address;
	while(count--) {
		phys_free_page(address_i);
		address_i += PAGE_SIZE;
	}
	return 0;
}

static struct pagemap *avl_map(uint_t required_pages)
{
	struct pagemap *map;
	list_for(pagemaps, map) {
		if(map->free_pages >= required_pages)
			return map;
	}
	debug("Out of physical memory! (needed %u pages)\n", required_pages);
	return null;
}

void *phys_get_pages(uint_t count)
{
	uint_t i, free_count = 0, start = 0;
	char in_extent = 0;
	//Find a map which has free pages first
	struct pagemap *map = avl_map(count);

	if(map == null)
		return null;

	for(i = 0; i < MAP_NR_PAGES; i++) {
		if(!get_bit(map->bitmap, i)) {
			if(!in_extent) {
				in_extent = 1;
				start = i;
			}
			free_count++;
			if(free_count == count) {
				uint_t j;
				//Reserve the pages we found
				for(j = start; j <= i; j++) {
					set_bit(map->bitmap, j);
					map->free_pages--;
				}
				return address_wr_from_bit(map->base, start);
			}
		} else {
			in_extent = 0;
			free_count = 0;
		}
	}
	return null;
}


static struct vfs_node *page_sys, *page_free_sys, *page_total_sys;

static int default_write(void *buf, uint_t len)
{
	return ENOSUP;
}

static void *default_read(uint_t *len)
{
	*len = 0;
	return null;
}

static void *page_free_read(uint_t *len)
{
	char buf[10];
	*len = format_string(buf, 10, "%u", phys_count_free());
	char *ret_buf = (char *)kmalloc(*len, "pgsys");
	memcpy(ret_buf, buf, *len);
	return ret_buf;
}

static void *page_total_read(uint_t *len)
{
	char buf[10];
	*len = format_string(buf, 10, "%u", phys_total_count());
	char *ret_buf = (char *)kmalloc(*len, "pgsys");
	memcpy(ret_buf, buf, *len);
	return ret_buf;
}

int page_sys_init()
{
	initmsg("Creating page count interfaces..\n");
	page_sys = vfs_create(sys_root, "page");
	page_free_sys = vfs_create(page_sys, "free");
	page_total_sys = vfs_create(page_sys, "total");
	register_sys_interface(page_sys, default_read, default_write);
	register_sys_interface(page_free_sys, page_free_read, default_write);
	register_sys_interface(page_total_sys, page_total_read, default_write);
	return 0;
}
